import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, InternalAxiosRequestConfig, AxiosResponse } from "axios";
import { showFullScreenLoading, tryHideFullScreenLoading } from "@/components/Loading/fullScreen";
import { ElMessage } from "element-plus";
import { checkStatus } from "./helper/checkStatus";
import { useUserStore } from "@/stores/modules/user";
import { useTokenStore } from "@/stores/modules/token";
import router from "@/routers";
import { SYSTEM_CONFIG } from "@/config/system";
import { getQueryVariable, objectToUrlParams } from "@/utils/common";
import { messageErrorTip } from "@/utils/message-config";
import { ERROR_CODE } from "@/config/status-code";
import { refreshToken } from "@/api/modules/system/index";

const CancelToken = axios.CancelToken;
/**存放请求数据 */
const requestMap = new Map();
/**超时后清理前端token信息 重置路由 并跳转登录页 */
const tokenInvalidAfter = () => {
  const userStore = useUserStore();
  /**提示信息 */
  ElMessage.error({ message: "您的登录已失效,请重新登录" });
  /**调用退出方法清除全部信息 */
  // userStore.logout();
  /**清除前端路由 */
  // createWebRouter();
  /**刷新页面 */
  location.reload();
};
/**
 *  处理请求中字符串有左右空格的情况 均去除
 * @param params 参数
 * @since 2022/05/18 zhu_liangyu
 */
const trimParamHandle = (params: any) => {
  if (!params) return;
  Object.keys(params).forEach((key, index) => {
    //如果他是字符串 则去掉其左右空格
    if (typeof params[key] === "string") {
      params[key] = params[key].trim();
    }
  });
};

let refreshing = false, // 正在刷新标识，避免重复刷新
  waitQueue = [] as any[]; // 请求等待队列
/**处理重复提交请求 */
const handleRquestResubmit = {
  before(config: AxiosRequestConfig) {
    const uniqueCode =
      (config.method?.toLowerCase() || "") + config.baseURL + config.url + objectToUrlParams(config.params || {}, true);
    //说明这个请求池中存在这个请求，还在进行中并没有结束
    if (requestMap.get(uniqueCode)) {
      // 防止提交表单等时候重复发起同一个请求（所以这里只限制了post请求）
      if (config.method === "post") {
        // 取消重复请求（取消掉后面重复的请求）直接取消当前的请求，请求还未发出就被取消了，所以浏览器不会生成取消的记录
        config.cancelToken = new CancelToken(cancel => {
          cancel("请不要重新提交请求" + uniqueCode);
        }); //取消的是当前的请求
      }
    } else {
      //存贮请求的唯一标识作为健key: 对应的取消函数作为值 value
      config.cancelToken = new CancelToken(cancel => requestMap.set(uniqueCode, cancel));
    }
  },
  after(response: AxiosResponse<any>) {
    // 剔除请求池中完成的请求
    const uniqueCode =
      (response.config.method?.toLowerCase() || "") +
      response.config.baseURL +
      response.config.url +
      objectToUrlParams(response.config.params || {}, true);

    if (requestMap.get(uniqueCode)) {
      requestMap.delete(uniqueCode);
    }
  }
};

function hanlderBlobError(response: AxiosResponse<any>, config: AxiosRequestConfig) {
  if (response.data.type == "application/json") {
    let reader = new FileReader();
    reader.readAsText(response.data);
    reader.onload = e => {
      let result = "";
      if (e.target && e.target.result) {
        const errorData = JSON.parse(e.target.result as string);
        if (errorData && errorData.message) {
          result += " " + errorData.message;
        }
      }
      messageErrorTip(result);
      console.log(result);
    };
    // loadingText = "导出异常 请稍后再试"
  }
}

export interface CustomAxiosRequestConfig extends InternalAxiosRequestConfig {
  fullLoading?: boolean;
}

const config = {
  // 默认地址请求地址，可在 .env.** 文件中修改
  baseURL: SYSTEM_CONFIG.BASE_URL,
  // 设置超时时间
  timeout: SYSTEM_CONFIG.TIMEOUT,
  // 跨域时候允许携带凭证
  withCredentials: true
};

class RequestHttp {
  service: AxiosInstance;
  public constructor(config: AxiosRequestConfig) {
    // instantiation
    this.service = axios.create(config);

    /**
     * @description 请求拦截器
     * 客户端发送请求 -> [请求拦截器] -> 服务器
     * token校验(JWT) : 接受服务器返回的 token,存储到 vuex/pinia/本地储存当中
     */
    this.service.interceptors.request.use(
      (config: CustomAxiosRequestConfig) => {
        /**获取token-state */
        const tokenStore = useTokenStore();
        /**当前请求需要显示 loading，在 api 服务中通过指定的第三个参数: { fullLoading: true } 来控制 */
        // config.fullLoading ?? (config.fullLoading = true);
        config.fullLoading && showFullScreenLoading();
        if (config.headers && typeof config.headers.set === "function") {
          /**添加Authorization */
          if (tokenStore.access_token) config.headers.set("Authorization", tokenStore.token_type + " " + tokenStore.access_token);
          /**添加Content-Type */
          if (!config.headers["Content-Type"]) config.headers.set("Content-Type", "application/json;charset=UTF-8");
          /**添加JxudpAppId */
          if (!config.headers["JxudpAppId"]) config.headers.set("JxudpAppId", SYSTEM_CONFIG.APP_ID);
        }
        /**添加一个时间戳 */
        if (!config.params) config.params = {};
        config.params._t = new Date().getTime();
        /**以下接口不需要传递token */
        if (config.url === "/oauth/token" || config.url === "/getSysConfig") {
          delete config.headers.Authorization;
        }
        trimParamHandle(config.params);
        handleRquestResubmit.before(config);
        return config;
      },
      (error: AxiosError) => {
        return Promise.reject(error);
      }
    );

    /**
     * @description 响应拦截器
     *  服务器换返回信息 -> [拦截统一处理] -> 客户端JS获取到信息
     */
    this.service.interceptors.response.use(
      (response: AxiosResponse) => {
        tryHideFullScreenLoading();
        handleRquestResubmit.after(response);
        return response;
      },
      async (error: AxiosError) => {
        tryHideFullScreenLoading();
        const tokenStore = useTokenStore();
        const response = error.response;
        const config = error.response.config;
        handleRquestResubmit.after(response);
        if (config.responseType == "blob") {
          hanlderBlobError(response, config);
        } else {
          // 请求超时 && 网络错误单独判断，没有 response
          if (error.message.indexOf("timeout") !== -1) messageErrorTip("请求超时！请您稍后重试");
          if (error.message.indexOf("Network Error") !== -1) messageErrorTip("网络错误！请您稍后重试");
          // 根据服务器响应的错误状态码，做不同的处理
          if (response.status == ERROR_CODE.UNAUTHORIZED) {
            /**获取刷新token */
            let refresh_token = tokenStore.refresh_token as string;
            /**令牌失效后 我们准备进行刷新 */
            if (refresh_token && response.data.path != "/oauth/token") {
              if (refreshing == false) {
                refreshing = true;
                //刷新token
                return refreshToken(refresh_token)
                  .then(res => {
                    let token = res.data.access_token;
                    /**设置access_token refresh_token */
                    tokenStore.$patch({
                      access_token: token,
                      refresh_token: res.data.refresh_token
                    });
                    waitQueue.forEach(callback => callback(token)); // 已刷新token，所有队列中的请求重试
                    waitQueue = [];
                    return this.service(config);
                  })
                  .catch(() => {
                    tokenInvalidAfter();
                  })
                  .finally(() => {
                    refreshing = false;
                  });
              } else {
                // 正在刷新token，返回未执行resolve的Promise,刷新token执行回调
                return new Promise(resolve => {
                  waitQueue.push(() => {
                    //config.headers['Authorization'] = token
                    //config.baseURL = '' // 请求重试时，url已包含baseURL
                    resolve(this.service(config));
                  });
                });
              }
            } else {
              tokenInvalidAfter();
            }
          } else {
            checkStatus(error);
          }
          // 服务器结果都没有返回(可能服务器错误可能客户端断网)，断网处理:可以跳转到断网页面
          if (!window.navigator.onLine) router.replace("/500");
        }
        return Promise.reject(error);
      }
    );
  }
  /**
   * @description: 请求
   * @param  url 请求地址
   * @param  params 参数
   * @param  _object config参数
   */
  get(url: string, params?: object, _object = {}) {
    return this.service.get(url, { ...params, ..._object });
  }
  post(url: string, params?: object | string, _object = {}) {
    return this.service.post(url, params, _object);
  }
  put(url: string, params?: object, _object = {}) {
    return this.service.put(url, params, _object);
  }
  delete(url: string, params?: any, _object = {}) {
    return this.service.delete(url, { params, ..._object });
  }
  download(url: string, params?: any, _object = {}) {
    return this.service.post(url, params, { ..._object, responseType: "blob" });
  }
}

export default new RequestHttp(config);
